/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.lite.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Map one element of type A to many elements of type B.
 * 
 * Notes:
 * Not Thread Safe.
 * Does not allow duplicates of A.
 * Does not allow duplicate of B for a given A.
 * 
 * @author Sony Mathew
 */
public class One2ManyMap<A, B> implements Serializable {

    private static final long serialVersionUID = 1047468697212816777L;
    
    private Map<A, Set<B>> mapA2B = new LinkedHashMap<A, Set<B>>();
    
    /**
     * Add another A to B relationship. 
     * 
     * author Sony Mathew
     */
    public void add(A a, B b) {
        Set<B> bs = mapA2B.get(a);
        if (bs == null) {
            bs = new LinkedHashSet<B>();
            mapA2B.put(a, bs);
        }
        bs.add(b);        
    }
    
    /**
     * Indicate whether there exists a mapping from a to b.
     *  
     * author Sony Mathew
     */
    public boolean exists(A a, B b) {
        Set<B> bs = mapA2B.get(a);
        if (bs == null) {
            return false;
        }
        return bs.contains(b);
    }
    
    /**
     * Remove this A to B relationship.
     * 
     * author Sony Mathew
     */
    public void remove(A a, B b) {
        Set<B> bs = mapA2B.get(a);
        if (bs != null) {
            bs.remove(b);
            if (bs.isEmpty()) {
                mapA2B.remove(a);
            }
        }
    }
    
    /**
     * Removes all B elements mapped to the given A.
     * 
     * author Sony Mathew
     */
    public void remove(A a) {
        mapA2B.remove(a);
    }
    
    /**
     * Get the set of B elements mapped to the given A.
     * Can be null if no mappings have been added for this A.
     * 
     * author Sony Mathew
     */
    public Set<B> get(A a) {
        return mapA2B.get(a);
    }
    
    /**
     * Obtain all the A elements (i.e. keys).
     * 
     * author Sony Mathew
     */
    public Set<A> allKeys() {
        return mapA2B.keySet();
    }

    /**
     * Returns a union of all B elements across all A keys.
     * If you want the Set of Bs per A use the get(A) method.
     * 
     * author Sony Mathew
     */
    public List<B> allValues() {
        List<B> allValues = new ArrayList<B>();
        Set<A> as = allKeys();
        for(A a : as) {
            Set<B> bs = get(a);
            if (bs != null) {
                allValues.addAll(bs);
            }
        }
        return allValues;        
    }
    
    /**
     * Obtain (readonly) the the Map of A to many Bs directly.
     * 
     * author Sony Mathew
     */
    public Map<A, Set<B>> getMap() {
        return Collections.unmodifiableMap(mapA2B);
    }
    
    /**
     * Removes everything.
     * 
     * author Sony Mathew
     */
    public void clear() {
        mapA2B.clear();
    }
}
